package controller

import (
	"encoding/json"
	"errors"
	"fmt"
	"gin/queue"
	"gin/response"
	"gin/service"
	"gin/transcoder/ffmpeg"
	"gin/validate"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"github.com/spf13/viper"
	teamones_helper "github.com/teamones-open/go-helper"
	"image"
	"mime/multipart"
	"os"
	"strings"
	"time"
)

var (
	uploadFileKey  = "Filedata"
	ffmpegBinPath  = ""
	ffprobeBinPath = ""
	ImageType      = []interface{}{"gif", "jpeg", "jpg", "bmp", "png"}
	VideoType      = []interface{}{"mov", "mp4", "avi", "wmv", "flv"}
	AudioType      = []interface{}{"mp3"}
	FileType       = []interface{}{"pdf", "ppt", "pptx", "doc", "docx", "xls", "xlsx", "txt", "zip", "rar", "7z"} // 文件上传支持常见的文件格式
	DirectoryType  = []interface{}{"specify", "monthly", "permanent"}                                             // specify：指定路径，monthly：按月存储，permanent：永久固定存储

	ErrVideoWHLengthError = errors.New("The width of the video must be a multiple of 32 and the height must be a multiple of 2")
)

func UploadMedia(ctx *gin.Context) {

	ffmpegBinPath = viper.GetString("ffmpeg.ffmpegBinPath")
	ffprobeBinPath = viper.GetString("ffmpeg.ffprobeBinPath")

	var err error

	_, err = ctx.FormFile(uploadFileKey)
	imageSize := ctx.PostForm("size")

	// 额外文件存储目录参数
	directoryType := ctx.PostForm("directory_type")
	directoryName := ctx.PostForm("directory_name")

	// 是否关闭压缩
	useCompress := "yes"
	closeCompress := ctx.PostForm("close_compress")
	if closeCompress != "" {
		useCompress = "no"
	}

	var directoryParam validate.DirectoryParam
	if directoryType != "" {
		// 文件目录配置参数不为空
		if teamones_helper.InArray(directoryType, DirectoryType) {
			switch directoryType {
			case "specify":
				// 指定路径, directory_name 不能为空，例如 project/shenyinwangzuo
				if directoryName != "" {
					directoryParam.DirectoryName = directoryName + "/"
				} else {
					response.Fail(ctx, nil, "Specify type mode the directory_name parameter cannot be empty.")
					return
				}
				break
			case "monthly":
				// 按月自动生成本月文件夹 为 directory_name 参数
				if directoryName != "" {
					directoryParam.DirectoryName = directoryName + "/" + time.Now().Format("200601") + "/"
				} else {
					directoryParam.DirectoryName = time.Now().Format("200601") + "/"
				}
				break
			default:
				// 永久存储，directory_name 为空
				directoryParam.DirectoryName = ""
				break
			}
		} else {
			response.Fail(ctx, nil, "Directory type can only be one of specify, monthly and permanent.")
			return
		}
	} else {
		// 没有传参则默认为永久存储
		directoryParam.DirectoryType = "permanent"
		directoryParam.DirectoryName = ""
	}

	if err != nil {
		//ignore
		response.Fail(ctx, nil, err.Error())
		return
	} else {
		mediaMetadataItems := make([]validate.MediaMetadata, 0)
		var mediaMetadata validate.MediaMetadata
		for _, file := range ctx.Request.MultipartForm.File["Filedata"] {
			mediaMetadata, err = DealMediaItem(ctx, file, imageSize, directoryParam, useCompress)
			if err == nil {
				mediaMetadataItems = append(mediaMetadataItems, mediaMetadata)
			}
		}

		if err != nil {
			// 错误写入日志
			logrus.WithFields(logrus.Fields{
				"route": ctx.Request.URL.Path,
				"request_param": logrus.Fields{
					"image_size": imageSize,
				},
			}).Error(err.Error())

			if err == ErrVideoWHLengthError {
				response.FailWithCode(ctx, nil, err.Error(), -20700010)
				return
			}

			// 返回错误response
			response.Fail(ctx, nil, err.Error())
		} else {
			response.Success(ctx, gin.H{"data": mediaMetadataItems}, "Upload successful")
		}
	}
}

// 移动文件到临时文件夹
func MoveFileToTempFolder(ctx *gin.Context, File *multipart.FileHeader, fileExt string) (tempPath string, folderName string, err error) {

	// 文件名称
	folderName = teamones_helper.RandStringBytesMaskImprSrcUnsafe(10) + "_" + teamones_helper.GenerateMd5(time.Now().String())

	// 临时文件名
	dstName := folderName + "." + fileExt

	// 临时存放地址
	tempPath = viper.GetString("server.staticPath") + "temp/" + dstName

	// 拷贝到对应文件夹
	err = ctx.SaveUploadedFile(File, tempPath)

	return
}

// 处理单个媒体数据
func DealMediaItem(ctx *gin.Context, File *multipart.FileHeader, imageSize string, directoryParam validate.DirectoryParam, useCompress string) (mediaMetadata validate.MediaMetadata, err error) {

	// 文件后缀名
	fileExt := teamones_helper.GetFileExt(File.Filename)

	if teamones_helper.InArray(fileExt, ImageType) || teamones_helper.InArray(fileExt, VideoType) || teamones_helper.InArray(fileExt, AudioType) || teamones_helper.InArray(fileExt, FileType) {

		tempPath, folderName, MoveErr := MoveFileToTempFolder(ctx, File, fileExt)

		if MoveErr != nil {
			err = MoveErr
			return
		}

		// 获取当前文件的md5值
		fileMd5 := teamones_helper.GetFileMd5(tempPath)

		// 判断当前md5文件是否存在
		checkResult, mediaDBData := service.CheckFileMd5(fileMd5)

		if checkResult {
			// 存在直接返回
			jsonStr, _ := json.Marshal(mediaDBData.Param)
			_ = json.Unmarshal(jsonStr, &mediaMetadata)

			// 存在之间删除临时文件
			err = os.Remove(tempPath)
		} else {
			// 只支持图片、视频、音频处理
			mediaStatus := ""

			// 遍历赋值
			mediaMetadata.Md5 = fileMd5
			mediaMetadata.Md5Name = folderName
			mediaMetadata.Ext = fileExt
			mediaMetadata.FileSize = File.Size

			// 创建文件夹
			folderPath := "static/" + folderName
			err = teamones_helper.CreateDirectory("./" + folderPath)

			if err != nil {
				return
			}

			// 文件夹名称
			mediaMetadata.Folder = folderName
			mediaMetadata.Path = folderPath + "/"

			var pathPrefix string

			pathPrefix = directoryParam.DirectoryName
			if teamones_helper.InArray(fileExt, ImageType) {
				// 处理图片方法
				mediaMetadata.Type = "image"
				pathPrefix += "images"
				mediaMetadata.NamePrefix = folderName
				mediaMetadata, err = ConvertImage(mediaMetadata, tempPath, imageSize, directoryParam.DirectoryName, useCompress)
				mediaStatus = "no"
			} else if teamones_helper.InArray(fileExt, VideoType) {
				// 处理视频方法
				mediaMetadata.Type = "video"
				pathPrefix += "video"
				mediaMetadata, err = ConvertVideo(mediaMetadata, tempPath, directoryParam.DirectoryName, useCompress)
				mediaStatus = "no"
			} else if teamones_helper.InArray(fileExt, AudioType) {
				// 处理音频方法
				mediaMetadata.Type = "audio"
				pathPrefix += "audio"
				mediaMetadata.NamePrefix = folderName
				mediaMetadata, err = ConvertAudio(mediaMetadata, tempPath, directoryParam.DirectoryName, useCompress)
				mediaStatus = "no"
			} else if teamones_helper.InArray(fileExt, FileType) {
				// 常见文件方法
				mediaMetadata.Type = "file"
				pathPrefix += "file"
				mediaMetadata.NamePrefix = folderName
				mediaMetadata, err = ConvertFiles(mediaMetadata, tempPath, directoryParam.DirectoryName, useCompress)
				mediaStatus = "no"
			}

			if err == nil {
				// 没有错误保存media数据

				// 增加类型目录前缀
				mediaMetadata.Path = "static/" + pathPrefix + "/" + folderName + "/"

				addMedia := validate.AddMedia{}
				addMedia.Md5Name = folderName
				addMedia.Md5 = fileMd5
				addMedia.Type = mediaMetadata.Type
				addMedia.Status = mediaStatus
				addMedia.Param, _ = json.Marshal(mediaMetadata)
				err = service.CreateMedia(addMedia)

				if teamones_helper.InArray(fileExt, VideoType) {
					// 视频默认会转码为mp4 提前预设给第三方系统
					mediaMetadata.Ext = "mp4"
				}
			} else {
				// 存在错误删除临时文件
				_ = os.Remove(tempPath)

				// 删除文件夹
				_ = os.RemoveAll(folderPath)
			}
		}
	} else {
		err = errors.New(fmt.Sprintf("The media file type does not support the %s extension.", fileExt))
	}

	return
}

// 推送数据到队列
func PushDataToQueue(mediaType string, mediaMetadata validate.MediaMetadata, tempPath string, imageSize string, pathPrefix string, useCompress string) (err error) {

	// 执行转码,写入队列
	TranscodeVideoParam := validate.TranscodeVideoParam{}
	TranscodeVideoParam.TempPath = tempPath
	TranscodeVideoParam.Folder = mediaMetadata.Folder
	TranscodeVideoParam.Ext = mediaMetadata.Ext
	TranscodeVideoParam.Path = mediaMetadata.Path
	TranscodeVideoParam.Md5Name = mediaMetadata.Md5Name
	TranscodeVideoParam.FileSize = mediaMetadata.FileSize
	TranscodeVideoParam.PathPrefix = pathPrefix + mediaType
	TranscodeVideoParam.UseCompress = useCompress

	switch mediaType {
	case "audio":
		TranscodeVideoParam.Type = "audio"
		break
	case "file":
		TranscodeVideoParam.Type = "file"
		break
	case "images":
		TranscodeVideoParam.Type = "images"
		TranscodeVideoParam.ImageSize = imageSize
		break
	case "video":
		TranscodeVideoParam.Type = viper.GetString("ffmpeg.mode") // "video_transcode" 为本地转码

		if TranscodeVideoParam.Type == "video" {
			err = os.Rename(tempPath, "./"+mediaMetadata.Path+mediaMetadata.Md5Name+"."+mediaMetadata.Ext)
		}

		TranscodeVideoParam.Duration = mediaMetadata.Duration
		TranscodeVideoParam.Height = mediaMetadata.Height
		TranscodeVideoParam.Width = mediaMetadata.Width
		break
	default:
		return
	}

	TranscodeVideoParamJson, err := json.Marshal(TranscodeVideoParam)

	err = queue.SendMediaProcessingTask(TranscodeVideoParamJson)
	return
}

// 处理图片
func ConvertImage(mediaMetadata validate.MediaMetadata, tempPath string, imageSize string, pathPrefix string, useCompress string) (imageMetadata validate.MediaMetadata, err error) {
	imageMetadata = mediaMetadata

	// 获取图片宽高
	file, _ := os.Open(tempPath)
	imgConfig, _, imgConfigErr := image.DecodeConfig(file)
	if imgConfigErr != nil {
		err = imgConfigErr
		return
	}

	imageMetadata.Width = imgConfig.Width
	imageMetadata.Height = imgConfig.Height
	_ = file.Close()

	if imageMetadata.Ext == "gif" {
		// gif 格式不处理直接拷贝
		err = os.Rename(tempPath, "./"+mediaMetadata.Path+imageMetadata.Md5Name+"_origin."+imageMetadata.Ext)
		imageMetadata.Size = "origin"
	} else {
		// 处理更多尺寸
		if imageSize != "" {
			imageMetadata.Size = "origin," + imageSize
		} else {
			imageMetadata.Size = "origin"
		}
	}

	// 没有错误同步到COS
	if err == nil {
		// 推送到队列继续处理
		err = PushDataToQueue("images", imageMetadata, tempPath, imageSize, pathPrefix, useCompress)
	}

	return
}

// 处理音频
func ConvertAudio(mediaMetadata validate.MediaMetadata, tempPath string, pathPrefix string, useCompress string) (audioMetadata validate.MediaMetadata, err error) {
	audioMetadata = mediaMetadata
	err = os.Rename(tempPath, "./"+audioMetadata.Path+audioMetadata.Md5Name+"."+audioMetadata.Ext)

	// 没有错误同步到COS
	if err == nil {
		// 推送到队列继续处理
		err = PushDataToQueue("audio", audioMetadata, tempPath, "", pathPrefix, useCompress)
	}

	return
}

// 处理常见的文件
func ConvertFiles(mediaMetadata validate.MediaMetadata, tempPath string, pathPrefix string, useCompress string) (fileMetadata validate.MediaMetadata, err error) {
	fileMetadata = mediaMetadata
	err = os.Rename(tempPath, "./"+fileMetadata.Path+fileMetadata.Md5Name+"."+fileMetadata.Ext)

	// 没有错误同步到COS
	if err == nil {
		// 推送到队列继续处理
		err = PushDataToQueue("file", fileMetadata, tempPath, "", pathPrefix, useCompress)
	}

	return
}

// 处理视频
func ConvertVideo(mediaMetadata validate.MediaMetadata, tempPath string, pathPrefix string, useCompress string) (videoMetadata validate.MediaMetadata, err error) {
	videoMetadata = mediaMetadata

	// ffmpeg 配置
	ffmpegConf := &ffmpeg.Config{
		FfmpegBinPath:   ffmpegBinPath,
		FfprobeBinPath:  ffprobeBinPath,
		ProgressEnabled: false,
	}

	// 获取当前视频参数
	metadata, err := ffmpeg.New(ffmpegConf).
		Input(tempPath).
		GetMetadata()

	if err != nil {
		return
	}

	// 找到有数据的的视频流流
	useStreamsIndex := 0
	for index := range metadata.Streams {
		if metadata.Streams[index].Height > 0 {
			useStreamsIndex = index
			break
		}
	}

	// 获取视频流数据
	stream := metadata.Streams[useStreamsIndex]

	// 视频时长
	videoMetadata.Height = stream.Height
	videoMetadata.Width = stream.Width

	if videoMetadata.Width%32 != 0 || videoMetadata.Height%2 != 0 {
		// 检测视频的宽度必须是32的倍数，高度必须是2的倍数
		err = ErrVideoWHLengthError
		return
	}

	videoMetadata.Duration = stream.Duration
	RateArray := strings.Split(stream.RFrameRrate, "/")
	videoMetadata.Rate = RateArray[0]
	videoMetadata.Thumb = "/" + videoMetadata.Folder + "/" + videoMetadata.Md5Name + "_0.jpg"

	// 推送到队列继续处理
	err = PushDataToQueue("video", videoMetadata, tempPath, "", pathPrefix, useCompress)

	return
}

// 通过Md5 Name 查找媒体
func SelectMediaByMd5Name(ctx *gin.Context) {
	var mediaMd5sFilter validate.MediaMd5sFilter
	err := ctx.ShouldBindJSON(&mediaMd5sFilter)
	if err != nil {
		response.Fail(ctx, nil, err.Error())
		return
	}

	mediaData, err := service.SelectMediaByMd5Name(mediaMd5sFilter.Md5Name)

	if err != nil {
		response.Fail(ctx, nil, err.Error())
		return
	}

	response.Success(ctx, gin.H{"data": mediaData}, "Select successfully.")
}

// 通过Md5 Name 删除媒体
func RemoveMediaByMd5Name(ctx *gin.Context) {
	var mediaMd5sFilter validate.MediaMd5sFilter
	err := ctx.ShouldBindJSON(&mediaMd5sFilter)
	if err != nil {
		response.Fail(ctx, nil, err.Error())
		return
	}

	err = service.RemoveMediaByMd5Name(mediaMd5sFilter.Md5Name)
	if err != nil {
		response.Fail(ctx, nil, err.Error())
		return
	}

	response.Success(ctx, gin.H{}, "Delete successfully.")
}

func UpdateMediaExtByMd5Name(ctx *gin.Context) {
	var v validate.UpdateMediaExtByMd5Name
	err := ctx.ShouldBindJSON(&v)
	if err != nil {
		response.Fail(ctx, nil, err.Error())
		return
	}

	media, err := service.UpdateParamByMd5Name(v.Md5Name, v.Ext)
	if err != nil {
		response.Fail(ctx, nil, err.Error())
		return
	}

	response.Success(ctx, gin.H{"media": media}, "update successfully.")
}
